use core::fmt;
use core::arch::asm;
use crate::mem_io;
pub struct SerialPort {
    port: u16,
}

impl SerialPort {
    pub const fn new(port: u16) -> SerialPort {
        SerialPort { port }
    }
    
    pub fn init(&self) {
        unsafe {
            outb(self.port + 1, 0x00); // Disable interrupts
            outb(self.port + 3, 0x80);  // Enable DLAB
            outb(self.port + 0, 0x03);  // Set divisor (lo byte)
            outb(self.port + 1, 0x00);  //     (hi byte)
            outb(self.port + 3, 0x03);  // 8 bits, no parity, one stop
            outb(self.port + 2, 0xC7);  // Enable FIFO
        }
    }
    
    pub fn write_byte(&self, byte: u8) {
        unsafe {
            while inb(self.port + 5) & 0x20 == 0 {}
            outb(self.port, byte);
        }
    }
}

impl fmt::Write for SerialPort {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for byte in s.bytes() {
            self.write_byte(byte);
        }
        Ok(())
    }
}

unsafe fn outb(port: u16, value: u8) {
    mem_io::write_u8(port as *mut u8, value);
}

unsafe fn inb(port: u16) -> u8 {
    mem_io::read_u8(port as *const u8)
}

pub static mut COM1: SerialPort = SerialPort::new(0x3F8);

#[macro_export]
macro_rules! serial_print {
    ($($arg:tt)*) => {
        $crate::serial::_print(format_args!($($arg)*))
    };
}

#[macro_export]
macro_rules! serial_println {
    () => ($crate::serial_print!("\n"));
    ($($arg:tt)*) => ($crate::serial_print!("{}\n", format_args!($($arg)*)));
}

#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
    use core::fmt::Write;
    unsafe {
        COM1.write_fmt(args).unwrap();
    }
}
pub fn init() {
    unsafe {
        COM1.init();
    }
}